package classfile

import (
	"fmt"
	. "jvm/typedef"
)

type Attribute interface {
	readInfo(reader *BytecodeReader)
	Print()
}

func newAttributes(reader *BytecodeReader, constantPool *ConstantPool) []Attribute {
	attributesCount := reader.readU2()
	attributes := make([]Attribute, attributesCount)
	for i := 0; i < int(attributesCount); i++ {
		attributeNameIndex := reader.readU2()
		attributeName := constantPool.getUtf8(attributeNameIndex)
		attributes[i] = newAttribute(attributeName, constantPool)
		attributes[i].readInfo(reader)
	}
	return attributes
}

func newAttribute(attributeName string, constantPool *ConstantPool) Attribute {
	switch attributeName {
	case "Code":
		return &AttributeCode{
			constantPool: *constantPool,
		}
	case "Deprecated":
		return &AttributeDeprecated{}
	case "ConstantValue":
		return &AttributeConstantValue{}
	case "Exceptions":
		return &AttributeExceptions{}
	case "LineNumberTable":
		return &AttributeLineNumberTable{}
	case "LocalVariableTable":
		return &AttributeLocalVariableTable{}
	case "SourceFile":
		return &AttributeSourceFile{}
	default:
		return &AttributeUnparsed{}
	}

}

// *********************************************************************
type AttributeCode struct {
	constantPool   ConstantPool
	maxStack       U2
	maxLocals      U2
	code           []U1
	exceptionTable []ExceptionTable
	attributes     []Attribute
}

type ExceptionTable struct {
	startPc   U2
	endPc     U2
	handlerPc U2
	catchType U2
}

func (self *AttributeCode) readInfo(reader *BytecodeReader) {
	reader.readU4()
	self.maxStack = reader.readU2()
	self.maxLocals = reader.readU2()
	codeLength := reader.readU4()
	self.code = make([]U1, codeLength)
	for i := 0; i < int(codeLength); i++ {
		self.code[i] = reader.readU1()
	}
	exceptionTableLength := reader.readU2()
	self.exceptionTable = make([]ExceptionTable, exceptionTableLength)
	for i := 0; i < int(exceptionTableLength); i++ {
		self.exceptionTable[i] = ExceptionTable{
			startPc:   reader.readU2(),
			endPc:     reader.readU2(),
			handlerPc: reader.readU2(),
			catchType: reader.readU2(),
		}
	}
	attributesCount := reader.readU2()
	self.attributes = make([]Attribute, attributesCount)
	for i := 0; i < int(attributesCount); i++ {
		attributeNameIndex := reader.readU2()
		attributeName := self.constantPool.getUtf8(attributeNameIndex)
		self.attributes[i] = newAttribute(attributeName, &self.constantPool)
		self.attributes[i].readInfo(reader)
	}

}
func (self *AttributeCode) Print() {
	fmt.Println("Code:")
	fmt.Printf("\tstack=%d, locals=%d\n", self.maxStack, self.maxLocals)
	fmt.Println("\tcode:", self.code)
	fmt.Println("\texception_table:", len(self.exceptionTable))
	for index, val := range self.exceptionTable {
		fmt.Printf("\t#%d{\n", index)
		fmt.Println("\tstart_pc:", val.startPc)
		fmt.Println("\tend_pc:", val.endPc)
		fmt.Println("\thandler_pc:", val.handlerPc)
		fmt.Println("\tcatch_type:", val.catchType)
		fmt.Println("\t}")
	}
	fmt.Println("attributes:", len(self.attributes))
	for index, val := range self.attributes {
		fmt.Printf("\t#%d{\n", index)
		val.Print()
		fmt.Printf("}\n")
	}
}

// getter
func (self *AttributeCode) MaxStack() U2 {
	return self.maxStack
}
func (self *AttributeCode) MaxLocals() U2 {
	return self.maxLocals
}
func (self *AttributeCode) Code() []U1 {
	return self.code
}

// *********************************************************************

type AttributeExceptions struct {
	exceptionIndexTable []U2
}

func (self *AttributeExceptions) readInfo(reader *BytecodeReader) {
	reader.readU4()
	length := reader.readU2()
	self.exceptionIndexTable = make([]U2, length)
	for i := 0; i < int(length); i++ {
		self.exceptionIndexTable[i] = reader.readU2()
	}
}
func (self *AttributeExceptions) Print() {
	fmt.Println("Exceptions:", len(self.exceptionIndexTable))
	for index, val := range self.exceptionIndexTable {
		fmt.Printf("#%d\t\t%d", index, val)
	}
}

// *********************************************************************
type AttributeDeprecated struct{}

func (self *AttributeDeprecated) readInfo(reader *BytecodeReader) {
	reader.readU4()
}
func (self *AttributeDeprecated) Print() {

}

type AttributeSynthetic struct{}

func (self *AttributeSynthetic) readInfo(reader *BytecodeReader) {
	reader.readU4()
}
func (self *AttributeSynthetic) Print() {

}

// *********************************************************************
type AttributeSourceFile struct {
	sourcefileIndex U2
}

func (self *AttributeSourceFile) readInfo(reader *BytecodeReader) {
	reader.readU4()
	self.sourcefileIndex = reader.readU2()
}
func (self *AttributeSourceFile) Print() {
	fmt.Printf("SourceFile:\t\t#%d\n", self.sourcefileIndex)
}

// *********************************************************************
type AttributeConstantValue struct {
	constantvalueIndex U2
}

func (self *AttributeConstantValue) readInfo(reader *BytecodeReader) {
	reader.readU4()
	self.constantvalueIndex = reader.readU2()
}
func (self *AttributeConstantValue) Print() {
	fmt.Printf("ConstantValue:\t\t#%d\n", self.constantvalueIndex)
}

// *********************************************************************
type AttributeLineNumberTable struct {
	lineNumberTable []AttributeLineNumber
}
type AttributeLineNumber struct {
	startPC    U2
	lineNumber U2
}

func (self *AttributeLineNumberTable) readInfo(reader *BytecodeReader) {
	reader.readU4()
	length := reader.readU2()
	self.lineNumberTable = make([]AttributeLineNumber, length)
	for i := 0; i < int(length); i++ {
		self.lineNumberTable[i] = AttributeLineNumber{
			startPC:    reader.readU2(),
			lineNumber: reader.readU2(),
		}
	}
}
func (self *AttributeLineNumberTable) Print() {
	fmt.Printf("LineNumberTable\n")
}

// *********************************************************************
type AttributeLocalVariableTable struct {
	localVariableTable []AttributeLocalVariable
}
type AttributeLocalVariable struct {
	startPc         U2
	length          U2
	nameIndex       U2
	descriptorIndex U2
	index           U2
}

func (self *AttributeLocalVariableTable) readInfo(reader *BytecodeReader) {
	reader.readU4()
	length := reader.readU2()
	self.localVariableTable = make([]AttributeLocalVariable, length)
	for i := 0; i < int(length); i++ {
		self.localVariableTable[i] = AttributeLocalVariable{
			startPc:         reader.readU2(),
			length:          reader.readU2(),
			nameIndex:       reader.readU2(),
			descriptorIndex: reader.readU2(),
			index:           reader.readU2(),
		}
	}
}
func (self *AttributeLocalVariableTable) Print() {
	fmt.Printf("LocalVariableTable\n")
}

// *********************************************************************
type AttributeUnparsed struct{}

func (self *AttributeUnparsed) readInfo(reader *BytecodeReader) {
	length := reader.readU4()
	for i := 0; i < int(length); i++ {
		reader.readU1()
	}
}
func (self *AttributeUnparsed) Print() {
	fmt.Printf("Unparsed\n")
}

// *********************************************************************
